#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
xprotocol 协议解析模块
协议格式:
    head    len     dtype   addr    cmd     payload crc
    2bytes  2bytes  2bytes  2bytes  2bytes  Nbytes  2btyes
"""

import struct
import binascii

class XProtocol:
    """
    xprotocol 协议解析类
    """
    # 头部标识，默认为 AABB
    DEFAULT_HEAD = b'\xFE\xED'
    
    # 固定字段大小
    HEAD_SIZE = 2
    LEN_SIZE = 2
    DTYPE_SIZE = 2
    ADDR_SIZE = 2
    CMD_SIZE = 2
    CRC_SIZE = 2
    
    # 整个帧的最小大小 (不包含负载)
    MIN_FRAME_SIZE = HEAD_SIZE + LEN_SIZE + DTYPE_SIZE + ADDR_SIZE + CMD_SIZE + CRC_SIZE
    
    def __init__(self):
        """初始化协议解析器"""
        pass
    
    def calc_crc16(self, data):
        """
        计算CRC16校验值
        使用Modbus CRC-16算法
        """
        crc = 0xFFFF
        for byte in data:
            crc ^= byte
            for _ in range(8):
                if crc & 0x0001:
                    crc = (crc >> 1) ^ 0xA001
                else:
                    crc = crc >> 1
        # 返回两个字节的CRC值
        return crc.to_bytes(2, byteorder='little')
    
    def build_packet(self, head, dtype, addr, cmd, payload=b''):
        """
        构建数据包
        
        参数:
            head: 帧头，2字节
            dtype: 数据类型，2字节整数
            addr: 地址，2字节整数
            cmd: 命令，2字节整数
            payload: 负载数据，字节数组
        
        返回:
            完整的数据包字节数组
        """
        # 检查帧头长度
        if isinstance(head, bytes) and len(head) != self.HEAD_SIZE:
            raise ValueError(f"帧头长度错误，应为{self.HEAD_SIZE}字节")
            
        # 计算数据包长度 (负载长度 + 固定字段长度)
        data_len = len(payload) + self.DTYPE_SIZE + self.ADDR_SIZE + self.CMD_SIZE+self.CRC_SIZE+self.LEN_SIZE +self.HEAD_SIZE  
        
        # 打包帧头和长度
        packet = head
        packet += struct.pack(">H", data_len)  # 小端序打包长度字段
        
        # 打包数据类型、地址和命令
        packet += struct.pack(">H", dtype)  # 小端序打包数据类型字段
        packet += struct.pack(">H", addr)   # 小端序打包地址字段
        packet += struct.pack(">H", cmd)    # 小端序打包命令字段
        
        # 添加负载数据
        packet += payload
        
        # 计算并添加CRC校验
        data_for_crc = packet  # 对整个数据包(不包含CRC)计算CRC
        crc = self.calc_crc16(data_for_crc)
        packet += crc
        
        return packet
    
    def parse_packet(self, data):
        """
        解析数据包
        
        参数:
            data: 字节数组
        
        返回:
            (packet_dict, consumed_bytes): 解析后的数据包字典和已处理的字节数
            如果数据不够解析一个完整的包，则返回 (None, 0)
        """

        # 检查是否有足够的数据来解析帧头
        if len(data) < self.HEAD_SIZE:
            return None, 0
        # 查找帧头
        head_pos = self._find_head(data)
        if head_pos == -1:
            # 没有找到帧头，消耗掉所有不可能是帧头开始的数据
            if len(data) > self.HEAD_SIZE:
                return None, len(data) - self.HEAD_SIZE + 1
            else:
                return None, 0

        # 移动到帧头位置
        data = data[head_pos:]
        
        # 检查数据是否足够长
        if len(data) < self.MIN_FRAME_SIZE:
            return None, 0
        # 解析帧头
        head = data[:self.HEAD_SIZE]

        
        # 解析长度字段
        len_field = struct.unpack(">H", data[self.HEAD_SIZE:self.HEAD_SIZE+self.LEN_SIZE])[0]
        
        # 计算整个数据包的长度
        total_len =len_field    # self.HEAD_SIZE + self.LEN_SIZE + len_field + self.CRC_SIZE
        
        # 检查数据是否足够长
        if len(data) < total_len:
            return None, 0
        
        # 解析数据类型、地址和命令
        dtype = struct.unpack(">H", data[self.HEAD_SIZE+self.LEN_SIZE:self.HEAD_SIZE+self.LEN_SIZE+self.DTYPE_SIZE])[0]
        addr = struct.unpack(">H", data[self.HEAD_SIZE+self.LEN_SIZE+self.DTYPE_SIZE:self.HEAD_SIZE+self.LEN_SIZE+self.DTYPE_SIZE+self.ADDR_SIZE])[0]
        cmd = struct.unpack(">H", data[self.HEAD_SIZE+self.LEN_SIZE+self.DTYPE_SIZE+self.ADDR_SIZE:self.HEAD_SIZE+self.LEN_SIZE+self.DTYPE_SIZE+self.ADDR_SIZE+self.CMD_SIZE])[0]
        
        # 计算负载的起始和结束位置
        payload_start = self.HEAD_SIZE + self.LEN_SIZE + self.DTYPE_SIZE + self.ADDR_SIZE + self.CMD_SIZE
        payload_end = total_len - self.CRC_SIZE
        
        # 解析负载
        payload = data[payload_start:payload_end]
        
        # 解析CRC
        crc = data[payload_end:payload_end+self.CRC_SIZE]
        
        # 验证CRC
        calc_crc = self.calc_crc16(data[:payload_end])
        crc_valid = (calc_crc == crc)
        
        # 构建数据包字典
        packet = {
            'head': head,
            'len': len_field,
            'dtype': dtype,
            'addr': addr,
            'cmd': cmd,
            'payload': payload,
            'crc': crc,
            'crc_valid': crc_valid
        }
        
        return packet, total_len
    
    def _find_head(self, data):
        """
        在数据中查找帧头
        
        参数:
            data: 字节数组
        
        返回:
            帧头在数据中的位置，如果没找到则返回-1
        """
        # 默认查找 FEED 帧头
        head = self.DEFAULT_HEAD
        
        for i in range(len(data) - self.HEAD_SIZE + 1):
            if data[i:i+self.HEAD_SIZE] == head:
                return i
        
        return -1 